import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ConfigService } from '@nestjs/config';
import { DocumentChunk } from './entities/document-chunk.entity';
import { Article } from './entities/article.entity';
import { v4 as uuidv4 } from 'uuid';
import OpenAI from 'openai';

@Injectable()
export class VectorService {
  private readonly logger = new Logger(VectorService.name);
  private openai: OpenAI;

  constructor(
    @InjectRepository(DocumentChunk)
    private readonly documentChunkRepository: Repository<DocumentChunk>,
    @InjectRepository(Article)
    private readonly articleRepository: Repository<Article>,
    private readonly configService: ConfigService,
  ) {
    this.initOpenAI();
  }

  private initOpenAI() {
    this.openai = new OpenAI({
      apiKey: this.configService.get('DATABASE_DASHSCOPE_API_KEY'),
      baseURL: this.configService.get('DATABASE_DASHSCOPE_API_URL'),
    });
  }

  /**
   * 简单的文本分块
   */
  private splitText(text: string, chunkSize: number = 1000, overlap: number = 200): string[] {
    const chunks: string[] = [];
    const sentences = text.split(/[。！？；\n]+/).filter(s => s.trim().length > 0);
    
    let currentChunk = '';
    for (const sentence of sentences) {
      if (currentChunk.length + sentence.length > chunkSize && currentChunk.length > 0) {
        chunks.push(currentChunk.trim());
        // 保留重叠部分
        const overlapText = currentChunk.slice(-overlap);
        currentChunk = overlapText + sentence;
      } else {
        currentChunk += sentence + '。';
      }
    }
    
    if (currentChunk.trim().length > 0) {
      chunks.push(currentChunk.trim());
    }
    
    return chunks;
  }

  /**
   * 生成文本嵌入向量
   */
  private async generateEmbedding(text: string): Promise<number[]> {
    try {
      // 尝试使用DashScope兼容的embedding模型
      const models = ['text-embedding-v1', 'text-embedding-v2', 'text-embedding-ada-002'];
      
      for (const model of models) {
        try {
          this.logger.log(`尝试使用embedding模型: ${model}`);
          const response = await this.openai.embeddings.create({
            model: model,
            input: text,
          });
          this.logger.log(`成功使用embedding模型: ${model}`);
          return response.data[0].embedding;
        } catch (modelError) {
          this.logger.warn(`embedding模型 ${model} 不可用:`, modelError.message);
          continue;
        }
      }
      
      // 如果所有模型都失败，尝试使用简单的文本相似度
      this.logger.warn('所有embedding模型都不可用，使用简单文本相似度');
      return this.generateSimpleEmbedding(text);
    } catch (error) {
      this.logger.error('生成向量失败:', error);
      // 返回简单文本相似度作为fallback
      return this.generateSimpleEmbedding(text);
    }
  }

  /**
   * 生成简单的文本向量（基于字符频率）
   */
  private generateSimpleEmbedding(text: string): number[] {
    // 创建一个简单的向量表示，基于字符频率
    const vector = new Array(256).fill(0);
    for (let i = 0; i < text.length; i++) {
      const charCode = text.charCodeAt(i) % 256;
      vector[charCode]++;
    }
    
    // 归一化向量
    const magnitude = Math.sqrt(vector.reduce((sum, val) => sum + val * val, 0));
    if (magnitude > 0) {
      return vector.map(val => val / magnitude);
    }
    
    return vector;
  }

  /**
   * 将文档内容分块并向量化
   */
  async processDocument(articleId: number, content: string, metadata: Record<string, any> = {}) {
    try {
      this.logger.log(`开始处理文档 ${articleId}，内容长度: ${content.length} 字符`);
      
      // 1. 文本分块
      const chunks = this.splitText(content);
      this.logger.log(`文档 ${articleId} 被分割为 ${chunks.length} 个块`);

      // 2. 生成向量
      const documentChunks: DocumentChunk[] = [];
      
      for (let i = 0; i < chunks.length; i++) {
        const chunk = chunks[i];
        this.logger.log(`正在处理第 ${i + 1}/${chunks.length} 个块，长度: ${chunk.length} 字符`);
        
        const embedding = await this.generateEmbedding(chunk);
        this.logger.log(`第 ${i + 1} 个块向量化完成，向量维度: ${embedding.length}`);
        
        const documentChunk = new DocumentChunk();
        documentChunk.content = chunk;
        documentChunk.embedding = embedding;
        documentChunk.chunkIndex = i;
        documentChunk.chunkSize = chunk.length;
        documentChunk.articleId = articleId;
        documentChunk.metadata = {
          ...metadata,
          chunkId: uuidv4(),
          totalChunks: chunks.length,
        };
        
        documentChunks.push(documentChunk);
      }

      // 3. 保存到数据库
      this.logger.log(`准备保存 ${documentChunks.length} 个文档块到数据库`);
      await this.documentChunkRepository.save(documentChunks);
      this.logger.log(`成功保存 ${documentChunks.length} 个文档块到数据库`);

      return documentChunks.length;
    } catch (error) {
      this.logger.error('文档向量化处理失败:', error);
      throw error;
    }
  }

  /**
   * 向量相似度搜索
   */
  async similaritySearch(query: string, limit: number = 5): Promise<DocumentChunk[]> {
    try {
      // 1. 将查询文本向量化
      const queryEmbedding = await this.generateEmbedding(query);
      this.logger.log('查询向量化完成');

      // 2. 从数据库获取所有文档块
      const allChunks = await this.documentChunkRepository.find({
        relations: ['article'],
      });

      if (allChunks.length === 0) {
        this.logger.warn('没有找到任何文档块');
        return [];
      }

      // 3. 计算余弦相似度
      const similarities = allChunks.map(chunk => {
        const similarity = this.cosineSimilarity(queryEmbedding, chunk.embedding);
        return {
          chunk,
          similarity,
        };
      });

      // 4. 按相似度排序并返回前N个，同时保存相似度信息
      const topResults = similarities
        .sort((a, b) => b.similarity - a.similarity)
        .slice(0, limit)
        .map(item => {
          // 将相似度信息保存到metadata中
          item.chunk.metadata = {
            ...item.chunk.metadata,
            similarity: item.similarity,
            query: query
          };
          return item.chunk;
        });

      this.logger.log(`找到 ${topResults.length} 个相关文档块，最高相似度：${(topResults[0]?.metadata?.similarity * 100).toFixed(1)}%`);
      return topResults;
    } catch (error) {
      this.logger.error('向量搜索失败:', error);
      throw error;
    }
  }

  /**
   * 计算余弦相似度
   */
  private cosineSimilarity(vecA: number[], vecB: number[]): number {
    if (vecA.length !== vecB.length) {
      throw new Error('向量维度不匹配');
    }

    let dotProduct = 0;
    let normA = 0;
    let normB = 0;

    for (let i = 0; i < vecA.length; i++) {
      dotProduct += vecA[i] * vecB[i];
      normA += vecA[i] * vecA[i];
      normB += vecB[i] * vecB[i];
    }

    normA = Math.sqrt(normA);
    normB = Math.sqrt(normB);

    if (normA === 0 || normB === 0) {
      return 0;
    }

    return dotProduct / (normA * normB);
  }

  /**
   * 删除文档的所有向量块
   */
  async deleteDocumentChunks(articleId: number): Promise<void> {
    try {
      await this.documentChunkRepository.delete({ articleId });
      this.logger.log(`删除了文档 ${articleId} 的所有向量块`);
    } catch (error) {
      this.logger.error('删除文档向量块失败:', error);
      throw error;
    }
  }

  /**
   * 获取文档的向量块数量
   */
  async getDocumentChunkCount(articleId: number): Promise<number> {
    return await this.documentChunkRepository.count({ where: { articleId } });
  }
} 